/**
 * Copyright 2011 Adrian Witas
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.abstractmeta.commandify.core.command;

import org.abstractmeta.commandify.command.Commands;
import org.abstractmeta.commandify.core.AbstractCommandify;
import org.abstractmeta.commandify.core.CommandsImpl;
import org.abstractmeta.commandify.core.command.collection.ListCommands;
import org.abstractmeta.commandify.core.command.util.CommandifyUtil;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * Represents a collection which implement commandify.
 * This enables all mutable collection method calls being encapsulated as method commands.
 * If collection value implements commandify then as long as it is possible to build cloned version build, it will be used
 * instead of actual value.
 *
 * @author Adrian Witas
 */
public class CommandifyCollection<T> extends AbstractCommandify<Collection<T>> implements Collection<T> {


    private final Collection<T> collection;
    private boolean indexBased;


    @SuppressWarnings("unchecked")
    public CommandifyCollection(Collection<T> collection) {
       this(Collection.class, collection);
    }


    @SuppressWarnings("unchecked")
    protected CommandifyCollection(Class owner, Collection<T> collection) {
        super(owner, getCommands(owner));
        this.collection = collection;
        addConstructorCommand();
        commandifyExistingCollectionItems();
        indexBased = List.class.equals(owner) || Collection.class.equals(owner);
    }


    @SuppressWarnings("unchecked")
    private static <T> Commands<T> getCommands(Class<T> owner) {
        if (List.class.equals(owner)) {
            return new ListCommands(owner);
        }
        return new CommandsImpl<T>(owner);
    }


    protected void commandifyExistingCollectionItems() {
        Commands<Collection<T>> commands = getCommands();
        for (T t : collection) {
            commands.add(getAddCommand(t));
        }
    }

    protected void addConstructorCommand() {
        getCommands().setConstructorCommand(new AbstractConstructorCommand<Collection<T>>() {
            @Override
            public Collection<T> execute() {
                return new LinkedList<T>();
            }
        });
    }

    public Collection<T> getCollection() {
        return collection;
    }

    @Override
    public int size() {
        return collection.size();
    }

    @Override
    public boolean isEmpty() {
        return collection.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return collection.contains(o);
    }

    @Override
    public Iterator<T> iterator() {
        return collection.iterator();
    }

    @Override
    public Object[] toArray() {
        return collection.toArray();
    }

    @Override
    public <T> T[] toArray(T[] array) {
        return collection.toArray(array);
    }

    public boolean isIndexBased() {
        return indexBased;
    }

    @Override
    public boolean add(final T t) {
        getCommands().add(getAddCommand(t));

        boolean result = collection.add(t);
        if (isIndexBased()) {
            int index = collection.size();
            CommandifyUtil.associateAccessor(t, this, index);
        } else {
            throw new UnsupportedOperationException();
        }
        return result;
    }


    protected AbstractMethodCommand<Collection<T>> getAddCommand(final T t) {

        return new AbstractMethodCommand<Collection<T>>("add", new Class[]{t.getClass()}, new Object[]{t}) {

            @Override
            public void execute(Collection<T> instance) {
                instance.add(getCommandValue(t));
            }


        };
    }


    @Override
    public boolean remove(final Object o) {
        getCommands().add(new AbstractMethodCommand<Collection<T>>("remove", new Class[]{o.getClass()}, new Object[]{o}) {

            @Override
            public void execute(Collection<T> instance) {
                instance.remove(o);
            }

        });
        CommandifyUtil.dissociateAccessor(o);
        return collection.remove(o);
    }


    @Override
    public boolean containsAll(Collection<?> objects) {
        return collection.containsAll(objects);
    }

    @Override
    public boolean addAll(Collection<? extends T> ts) {
        if (ts == null) return false;
        boolean result = ts.size() > 0;
        for (T t : ts) {
            result &= add(t);
        }
        return result;
    }

    @Override
    public boolean removeAll(Collection<?> objects) {
        if (objects == null) return false;
        boolean result = objects.size() > 0;
        for (Object o : objects) {
            result &= remove(o);
        }
        return result;
    }


    @Override
    public boolean retainAll(Collection<?> objects) {
        return collection.retainAll(objects);
    }

    @Override
    public void clear() {
        getCommands().add(new AbstractMethodCommand<Collection<T>>("clear") {

            @Override
            public void execute(Collection<T> instance) {
                instance.clear();
            }

        });
        CommandifyUtil.dissociateAccessor(collection);
        this.collection.clear();
    }


}
